package com.xxy.book.provider;

import cn.hutool.core.date.DateUtil;
import com.xxy.book.properties.JwtProperties;
import com.xxy.book.vo.AccessToken;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * @Author Xu XianYun
 * @Date 2023-01-08
 * @Description
 */
@Slf4j
@Component
public class JwtProvider {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 从请求头中拿到token
     */
    public String getToken(HttpServletRequest request) {
        return request.getHeader(jwtProperties.getRequestHeader());
    }

    /**
     * 根据用户信息生成token
     */
    public AccessToken createToken(UserDetails userDetails) {
        return createToken(userDetails.getUsername());
    }

    /**
     * 生成token
     * 参数是我们像放入 token 的字符串
     */
    public AccessToken createToken(String subject) {
        // 当前时间戳
        final Date now = new Date();

        //过期时间戳
        final  Date expirationDate = new Date(now.getTime() + jwtProperties.getExpirationTime() * 1000);

        //生成token
        String token = jwtProperties.getTokenPrefix() + Jwts.builder()
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, jwtProperties.getApiSecretKey())
                .compact();

        return AccessToken.builder()
                .username(subject)
                .token(token)
                .expirationTime(expirationDate)
                .build();
    }

    /**
     * 验证 token 是否有效 (判断 token 携带的用户名和时间)
     * <p>反解析出token中信息，然后与参数中的信息比较，再校验过期时间</p>
     *
     * @param token         客户端传入的token
     * @param userDetails   从数据库查询出的用户信息
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        //拿到 token 中的负载信息
        Claims claims = getClaimsFromToken(token);
        if (claims == null) {
            return false;
        }
        return claims.getSubject().equals(userDetails.getUsername()) && !isExpiredToken(claims);
    }

    /**
     * 刷新 token
     * 过滤器会对请求进行验证，所以这里可以不必验证
     *
     * @param oldToken 带 tokenHead 的 token
     */
    public AccessToken refreshToken(String oldToken) {
        // 去除 tokenHead, 得到 token
        String token = oldToken.substring(jwtProperties.getTokenPrefix().length());

        // jwt 反解析token
        Claims claims = getClaimsFromToken(token);

        //如果 token 在 30 分钟内刚刚刷新过，返回原 token
        if (tokenRefreshedJustBefore(claims)) {
            return AccessToken.builder()
                    .username(claims.getSubject())
                    .token(token)
                    .expirationTime(claims.getExpiration())
                    .build();
        } else {
            return createToken(claims.getSubject());
        }
    }

    /**
     * 从 token 中获取主题
     */
    public String getSubjectFromToken(String token) {
        Claims claims = getClaimsFromToken(token);

        if (claims == null) {
            return null;
        }

        return claims.getSubject();
    }

    /**
     * 判断 token 是否刚刚刷新(当前时间与过期时间点的差值小于 30 分钟)
     */
    private boolean tokenRefreshedJustBefore(Claims claims) {
        Date refreshDate = new Date();

        //刷新时间在创建时间的指定时间内
//        if (refreshDate.after(claims.getExpiration())
//                && refreshDate.before(DateUtil.offsetSecond(claims.getExpiration(), 1800))) {
//            return true;
//        }

        if (refreshDate.before(claims.getExpiration()) &&
                refreshDate.after(DateUtil.offsetSecond(claims.getExpiration(), -1800))) {
            return true;
        }

        return false;
    }

    /**
     * 从 token 中拿到负载信息
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims = null;

        try {
            claims = Jwts.parser()
                    .setSigningKey(jwtProperties.getApiSecretKey())
                    .parseClaimsJwt(token)
                    .getBody();
        } catch (Exception e) {
            log.error("JWT 反解析失败， Token 已过期或不正确: {}", token);
        }
        return claims;
    }



    /**
     * 判断token是否过期
     */
    private boolean isExpiredToken(Claims claims) {
        return claims.getExpiration().before(new Date());
    }
}
